/**
* @file
* @brief This file defines functions in utils.h
*/
#define _XOPEN_SOURCE // For crypt()

#include "utils.h"
#include "hash.h"

//----------------------
// Data structure
//----------------------

// MACRO HEIGHT
// An expression for one more than the height of a node in an AVL tree
// (evaluates to 0 if node == NULL).
// Side-effects:  evaluates its argument more than once
// #define HEIGHT(node) ((node) ? (node)->height : 0) // TODO: For M3


// updateRecord
// removeRecord
// findRecord
// containsRecord

/**
* @note must check if table exists before calling this function
* 4 cases
* If value is NULL but key not found, error key not found
* If value is NULL but key found, delete the key
* If Key not found, and value non Null, insert new value with key and value
* If Key found and value non Null, update key with value
* @returns -1 if key not found
* @returns 1 if inserted value
* @returns 2 if updated value
* @returns 3 if deleted value
*/
int insertValue(char* Table, char* Key, char* Value)
{
    // Get table index
    int t_index = findTableIndex(Table);
    bool keyExist = false; // initialize key as not existent
    bool firstElement = false; // check if working with first element
    // Check if Key exists in table
    struct Record *p, *q, *newRecord;
    // Initialize p to point to first in table
    p = topTable.table[t_index].head;
    // If an element has already been inserted
    if (p == NULL)
        firstElement = true; // inserting first element
    while (p != NULL)
    {
        if((strcmp(Key, p->name)) == 0)
        {
            keyExist = true;
            break;
        }
        p = p->left;
    }
    //-------------------------------------
    // Here, we know if key exists or not
    //-------------------------------------
    // If Key exists
    //-------------------------------------
    if (keyExist == true)
    {
        // Case 1: Delete the key
        if (Value == NULL)
        {
            deleteRecord(Key, t_index);
            topTable.table[t_index].numRecs--;
            return 3;
        }
        // Case 2: Update key with value
        else
        {
            updateRecord(Key, Value, t_index);
            return 2;
        }
    }
    //-------------------------------------
    // If key doesn't exist
    //-------------------------------------
    else
    {
        // Case 3: Return error key not found
        if (Value == NULL)
        {
            printf("Error: Key not found\n");
            return -1;
        }
        // Case 4: Insert new value with key and value
        else
        {
            // Create a new Record
            newRecord = createRecord(Key, Value);
            if (firstElement)
            {
                // Insert at beginning
                // append to the beginning of the head
                newRecord->left = NULL; //topTable.table[t_index]->head
                topTable.table[t_index].head = newRecord;
            }
            else
            {
                // Insert at beginning but point to next
                newRecord->left = topTable.table[t_index].head;
                topTable.table[t_index].head = newRecord;
            }
            topTable.table[t_index].numRecs++;
	    return 1;
        }
    }
    return 0;
}

// assumes table exists
int getValue(char* Table, char* Key, char* Value)
{
    // Get table index
    int t_index = findTableIndex(Table);
    bool keyExist = false; // initialize key as not existent
    bool firstElement = false; // check if working with first element
    // Check if Key exists in table
    struct Record *p, *q, *newRecord;
    // Initialize p to point to first in table
    p = topTable.table[t_index].head;
    // If an element has already been inserted
    while (p != NULL)
    {
        if((strcmp(Key, p->name)) == 0)
        {
            keyExist = true;
            break;
        }
        p = p->left;
    }
    //-------------------------------------
    // Here, we know if key exists or not
    //-------------------------------------
    // If Key exists
    //-------------------------------------
    if (keyExist == true)
    {
        // Case 1: Get the key
        getRecord(Table,  Key, Value, t_index);
        return 1;
    }
    //-------------------------------------
    // If key doesn't exist
    //-------------------------------------
    else
    {
        // Case 2: Return error key not found
        printf("Error: Key not found\n");
        return -1;
    }
    return 0;
}

void getRecord(char* Table, char* Key, char* Value, int t_index)
{
    struct Record *getRecord;
    getRecord = topTable.table[t_index].head;
    while (getRecord != NULL)
    {
        if (strcmp(getRecord->name, Key) == 0)
        {
            strcpy(Value, getRecord->value);
        }
/* printf("Key: %s with Value: %s\n", getRecord->name, getRecord->value);*/
        getRecord = getRecord->left;
    }
    return; // Not found
}

int deleteRecord(char* name, int t_index)
{
    struct Record *deleteRecord, *temp;
    deleteRecord = topTable.table[t_index].head;
    temp = deleteRecord;
    int count = 0;
    while (deleteRecord != NULL)
    {
        if ( strcmp(deleteRecord->name, name) == 0)
        {
            struct Record **prev;
            for (prev = &temp; *prev && *prev != deleteRecord; prev = &(*prev)->left)
            {
                count++;
                continue;
            }

            if (*prev) // If we are at pp
            {

                if (count == 0)
                {
                    // if deleting first element
                    temp = (*prev)->left;
                    free(topTable.table[t_index].head);
                    topTable.table[t_index].head = temp;
                    return 1;
                }
                temp = (*prev)->left; // t points to pp->next
                free(*prev); // free pp
                *prev = temp; // let pointer pointing to pp point to next
                return 1;
            }
        }
        deleteRecord = deleteRecord->left;
    }
    return 0; // Not found
}

void printRecord(int t_index)
{
    struct Record *printRecord;
    printRecord = topTable.table[t_index].head;
    while (printRecord != NULL)
    {
        printf("Key: %s with Value: %s\n", printRecord->name, printRecord->value);
        printRecord = printRecord->left;
    }
    return; // Not found
}

int updateRecord(char* name, char* value, int t_index)
{
    struct Record *updateRecord;
    updateRecord = topTable.table[t_index].head;
    while (updateRecord != NULL)
    {
        if ( strcmp(updateRecord->name, name) == 0)
        {
            strcpy(updateRecord->value, value);
            return 1;
        }
        updateRecord = updateRecord->left;
    }
    return 0; // Not found
}

struct Record* createRecord(char* name, char* value)
{
    struct Record *newRecord;
    newRecord = (struct Record *) malloc (sizeof(struct Record));
    newRecord->left = NULL;
    strcpy(newRecord->name, name);
    strcpy(newRecord->value, value);
    return newRecord;
};

/**
* This function finds and checks if a table exists in a given function
* It returns the table index number
*/
int findTableIndex(char* tname)
{
    int i = 0;
    for (i = 0; i < topTable.numTables; i++)
    {
        if( (strcmp(tname, topTable.table[i].name)) == 0)
        {
            return i; // table name found
        }
    }
    return 0; // table name not found
}




void initializeSortedArrayTable()
{
    topTable.numTables = 0;
    return;
}

/**
* This function returns 1 if table is inserted successfully, or 0 if it fails
*/
int insertTable(char* tname)
{
    if (topTable.numTables >= MAX_TABLES )
    {
        printf("Exceed Maximum Number of tables allowed\n");
        // exit(EXIT_FAILURE);
        return 0;
    }
    strcpy(topTable.table[topTable.numTables].name, tname);
    topTable.table[topTable.numTables].numRecs = 0; // initialize to 0 records
    topTable.table[topTable.numTables].head = NULL; // point BST to null initiall
    topTable.numTables++;
    return 1; // success
}

/**
* This function sorts the table array using bubble sort
*/
int sortTable()
{
    int i = 0, j = 0;
    char temp[MAX_TABLE_LEN + 1];
    for (i = 0; i < topTable.numTables; i++)
    {
        for (j = 0; j < topTable.numTables - i - 1; j++)
        {
            if ( strcmp(topTable.table[j].name,topTable.table[j+1].name ) > 0)
            {
                // Swap them
                // @note: Only swaps the names and not the values since this is a 1 time function
                // that is called after configuration file is read
                strcpy(temp,topTable.table[j].name);
                strcpy(topTable.table[j].name,topTable.table[j+1].name);
                strcpy(topTable.table[j+1].name, temp);
            }
        }
    }
    return 1; // successful sort
}

/**
* This function prints the table name in the order they are in the array
*/
void printTableName()
{
    int i = 0;
    for (i = 0; i < topTable.numTables; i++)
    {
        printf("%s\n", topTable.table[i].name);
    }
    return;
}

/**
* This function finds and checks if a table exists in a given function
* It returns 1 if it exists and 0 if it does not
*/
int findTableName(char* tname)
{
    int i = 0;
    for (i = 0; i < topTable.numTables; i++)
    {
        if( (strcmp(tname, topTable.table[i].name)) == 0)
        {
            return 1; // table name found
        }
    }
    return 0; // table name notge found
}

//---------------------
// Utility
//--------------------

/**
* This function gets a line from stdin and attach it to output with null terminating it
* @param output is the string buffer to be written on
* @param max is how long the string buffer is including '\0'
* @return 1 if successful, 0 if an error occurred such as message passed in was too long
*/
int getLine(char* output, int max)
{
    int i = 0;
    char c;
    while((c = getchar()) != EOF)
    {
	output[i] = c;
	if (c == '\n')
	{
	    output[i] = '\0';
	    break;
	}
	i++;
	if (i > (max-1)) // need to -1 to keep space for '\0'
	{
	    return 0; // input size was too long
	}
    }
    return 1; // getLine was successful
}

/**
* This function gets the arguments after the message is passed through the network
* The arguments has to be "," separated since values may contain " "
*/
int getArguments(char** arg, char* cmd)
{
    int i= 0;
    char *token, *string, *tofree;
    tofree = string = strdup(cmd);
    assert(string != NULL);
    while ( ((arg[i]) = strsep(&string, ",")) != NULL)
    {
        i++;
    }
/*
    Remember to free after calling this function,
    if free here, whatever arg points to is gone
    @note If uncomment this, will segfault
    free(tofree);
*/
    return i;
}

/**
* This function returns the number of digits contain by number
* @param number is the integer to count number of digits
* @return the number of digits of number
*/
int numDigits(int number)
{
    int count = 0; // initialize as 0
    while (number != 0)
    {
	number /= 10;
	count++;
    }
    if (count == 0)
	count++; // make count 1
    return count;
}


/**
* This function sends all of buffer till size len to sock
*/
int sendall(const int sock, const char *buf, const size_t len)
{
    size_t tosend = len;
    while (tosend > 0)
    {
	// (sockfd to send to, buffer which contains data, amount of info, flags)
	ssize_t bytes = send(sock, buf, tosend, 0);
	if (bytes <= 0)
	    break; // send() was not successful, so stop.
	tosend -= (size_t) bytes;
	buf += bytes;
    };
    return tosend == 0 ? 0 : -1;
}

/**
* This function receives buffer from sock till size buflen and stores into buf.
*/
int recvline(const int sock, char *buf, const size_t buflen)
{
    // status returns -1 if not successful in receiving any data
    //		      0 if new line received
    int status = 0; // Return status.
    size_t bufleft = buflen;
    // while there is space in the buffer to receive
    while (bufleft > 1) // and no errors and no newline received
    {
	// Read one byte from socket
	// (sockfd to read from, buf to receive data, receive 1 data, flags)
	ssize_t bytes = recv(sock, buf, 1, 0);
	if (bytes <= 0)
	{
	    // recv() was not successful, so stop.
	    status = -1;
	    break;
	}
	else if (*buf == '\n')
	{
	// Found end of line, so stop.
	*buf = 0; // Replace end of line with a null terminator.
	status = 0;
	break;
	}
	else
	{
	    // Keep going.
	    bufleft -= 1; // decrease space left in buffer
	    buf += 1; // increment position in buffer to store data
	}
    }
    *buf = 0; // add null terminator in case it's not already there.
    return status;
}

/* READN() ARE FOR MILESTONE 3 WHEN DEADLING WITH MULTIPLE CLIENTS
// Read "n" bytes from a descriptor.
ssize_t readn(int fd, void *vptr, size_t n)
{
    size_t nleft; // number of bytes left to read
    ssize_t nread; // number of bytes read
    char *ptr;
    ptr = vptr; // let ptr point to beginning of buffer given
    nleft = n; // number of bytes to read
        if ((nread = read(fd, ptr, nleft)) < 0)
        {
            if (errno == EINTR) // if interrupted by signal
                nread = 0;        // and call read() again
            else
                return(-1);
        }
       // else if (nread == 0) // nothing left to read
       //     break;                // EOF
	// else
        nleft -= nread;// update number left to read
        ptr   += nread;// mv ptr
    return(n - nleft);        // return number of bytes read
}

// This function reads from fd and stores it in ptr
ssize_t Readn(int fd, void *ptr, size_t nbytes)
{
    ssize_t n;
    if ((n = readn(fd, ptr, nbytes)) < 0)
    {
        perror("readn error");
        return (-1);
    }
    return(n);
}
*/

/**
* This function processes one line in the configuration file
* // @note This function is called by readconfig()
*/
int process_config_line(char *line, struct config_params *params,struct check_duplmis *check)
{
    // Ignore comments.
    if (line[0] == CONFIG_COMMENT_CHAR) // defined in util.h
	return 0;
    // Extract config parameter name and value.
    char name[MAX_CONFIG_LINE_LEN];
    char value[MAX_CONFIG_LINE_LEN];
    char extra[MAX_CONFIG_LINE_LEN];
    // Scan the str line, for the first 2 strings and store it into name and value
    // returns number of items in argument list successfully filled
    int items = sscanf(line, "%s %s %s\n", name, value, extra);
    // Line must contain 2 different parameters only
    if (items != 2)
    {
	// if the line is not an empty line
	if (items != -1)
	{
	    perror("Too much/little parameter names/values");
// NO ERROR SETTING IN SERVER errno = ERR_UNKNOWN; // Not enough parameter names and values or too much for config file 
	    return -1;
	}
	return 0; // it is just an empty line, continue
    }
    // Process this line.
    if (strcmp(name, "server_host") == 0) // if exactly the same str
    {
	// Check if host was already given
	if((check->host)==0)
	{
	    // Copy to server_host the value
	    strncpy(params->server_host, value, sizeof params->server_host);
	    check->host = 1;
	}
	else
	{
	    perror("Repeated server_host");
	    errno = ERR_UNKNOWN;
	    return -1;
	}
    }
    else if (strcmp(name, "server_port") == 0)
    {
	if((check->port)==0)
	{
	// TODO: MAKE SURE PORT NUMBER IS LESS THAN MAX_PORT_LEN
		// Copy to server port the value in int
		params->server_port = atoi(value);
		check->port = 1;
	}
	else
	{
	    perror("Repeated server_port");
//	    errno=ERR_UNKNOWN;
	    return -1;
	}
    }
    else if (strcmp(name, "username") == 0)
    {
	if((check->username)==0)
	{
	    // Copy to username the value in str
	    strncpy(params->username, value, sizeof params->username);
	    check->username = 1;
	}
	else
	{
	    perror("Repeated username");
	    errno=ERR_UNKNOWN;
	    return -1;
	}
    }
    else if (strcmp(name, "password") == 0)
    {
	if((check->password)==0)
	{
	    // Copy to password the value in str
	    strncpy(params->password, value, sizeof params->password);
	    check->password = 1;
	}
	else
	{
	    perror("Repeated password");
	    errno=ERR_UNKNOWN;
	    return -1;
	}
    }
// TODO: NEED A WAY TO CHECK FOR DUPLICATE TABLES!!  NEED STORE ALL PREVIOUS TABLE INFORMATION IN A LINKED LIST
    else if (strcmp(name, "table") == 0)
     {
	// If inserting first table
	if(check->table == 0)
	{
	    if(!checkName(value))
	    {
		perror("Table name error");
/* errno = ERR_UNKNOWN; error checking in storage.h only*/
		return -1;
	    }
	    else
	    {
		insertTable(value); // insert the new table
		check->table++;
	    }
	}
	// If inserting second table onwards
	else
	{
	    int i = 0;
	    // Check the table name
            if(!checkName(value))
            {
                perror("Table name error");
/* errno = ERR_UNKNOWN; */
                return -1;
            }
	    // Check if it exists
	    if (findTableName(value))
	    {
		    perror("Duplicated Table");
		    printf("Duplicated table is %s\n", value);
/* errno=ERR_UNKNOWN;*/
		    return -1;
	    }
	    // No duplicates, insert new table
	    if(!insertTable(value)) // returns 0 if exceeded limits, returns 1 if successful
	    {
		// Will be in here if exceeded table limits
		return -1;
	    }
	    else
	    {
		check->table++;
	    }
	}
    }
    else if (strcmp(name, "data_directory") == 0)
    {
	strncpy(params->data_directory, value, sizeof params->data_directory);
    }
    // Ignore unknown configuration parameters
    else
    {
	perror("Not a valid parameter name");
	errno=ERR_UNKNOWN;
	return -1;
    }
        return 0;
}
/// This function reads from the config file
int read_config(const char *config_file, struct config_params *params,struct check_duplmis *check)
{
    int error_occurred = 0; // keep track if error occurs
    // Open file for reading.
    FILE *file = fopen(config_file, "r");
    if (file == NULL)
    {
	error_occurred = 1;
	errno = ERR_UNKNOWN; // The file does not exist
    }
    // Process the config file as long as no error has occurred and it is not the end of file
    while (!error_occurred && !feof(file))
    {
	// Read a line from the file.
	char line[MAX_CONFIG_LINE_LEN];
	// reads from file (line-1) characters or until newline is found
	// and stores into line. Automatically handles null terminates
	// and returns pointer to line on success
	char *l = fgets(line, sizeof line, file);
	// Process the line.
	if (l == line) // If success getting this line, do process_config_line
	{
	    if(process_config_line(line, params,check) == 0)
		continue; // do nothing
	    else
		error_occurred = 1; // an error has occurred in process_config_line
	}
	// If there was no line processed, it may be the end of file, or a commented line
	else if (!feof(file))
	    error_occurred = 1;
    }
//    if((check->host)==0)
    // Check to see if all values are given
    if (!(check->host) || !(check->port) || !(check->username) || !(check->password) || !(check->table))
    {
	error_occurred = 1;
	perror("There are missing configuration parameters");
	printf("host %d, port %d, username %d, password %d, table %d\n",check->host, check->port, check->username, check->password,
	check->table);
    }
    return error_occurred ? -1 : 0;
}

/// To log messages
void logger(FILE *file, char *message)
{
    if (file == NULL) // No logging needed
    {
	return;
    }
    fprintf(file,"%s",message);
    fflush(file);
}

/// This function generates an encrypted password
char *generate_encrypted_password(const char *passwd, const char *salt)
{
    if(salt != NULL)
	return crypt(passwd, salt);
    else
	return crypt(passwd, DEFAULT_CRYPT_SALT);
}

/**
* This function closes a file if logger was 2
* @param logger is the logging number defined
* @param serverFile is the file to be closed, 1 is if it the serverFile, 0 if it is the clientFile
*/
void Fclose(int logger, int serverFile)
{
    if (serverFile)
    {
	if (logger == 2)
	    fclose(fileServer);
    }
    else
    {
	if (logger == 2)
	    fclose(fileClient);
    }
}

/**
* This function prints the menu to prompt the user to enter his selection
*/
void menu()
{
    printf("---------------------\n 1) Connect\n 2) Authenticate\n 3) Get\n 4) Set\n 5) Disconnect\n 6) Exit\n---------------------\n");
    printf("Please enter your selection: ");
    return;
}

/**
* This is the constructor for structure check
*/
int initialCheck( struct check_duplmis *check)
{
    check->host = 0;
    check->port = 0;
    check->username = 0;
    check->password = 0;
    check->table = 0;
    return 0;
}

/**
* This function ensures that the string msg does only contains alphanumeric characters or space
* It is used for checking record values
* @return 1 if it satisfies all requirements
*         0 if it does not
*/
int checkValue(char *msg)
{
    char* checkStr = msg;
    while(*checkStr != '\0')
    {

        if (*checkStr == ' ')
            checkStr++;
        // A-Z
        else if ((*checkStr >= 65) && (*checkStr <= 90))
            checkStr++;
        // a-z
        else if ((*checkStr >= 97) && (*checkStr <= 122))
            checkStr++;
        // 0-9
        else if ((*checkStr >= 48) && (*checkStr <= 57))
            checkStr++;
        // If here, it is not alphanumeric
        else return 0;
    }
    // Satisfy all requirements (alphanumeric characters and  spaces)
    return 1;
}

/**
* This function ensures that the string msg does only contains alphanumeric characters
* It is used for checking table names and keys
* @return 1 if it satisfies all requirements
*         0 if it does not
*/
int checkName(char *msg)
{
    char* checkStr = msg;
    while(*checkStr != '\0')
    {
        // Alphanumeric
        // A-Z
        if (*checkStr == ' ')
            return 0;
	// A-Z
        if ((*checkStr >= 65) && (*checkStr <= 90))
            checkStr++;
        // a-z
        else if ((*checkStr >= 97) && (*checkStr <= 122))
            checkStr++;
        // 0-9
        else if ((*checkStr >= 48) && (*checkStr <= 57))
            checkStr++;
        // If here, it is not alphanumeric
        else return 0;
    }
    // Satisfy all requirements (alphanumeric characters, no spaces)
    return 1;
}


/**
* This function returns the first pointer to the next occurence of \r\n, \r, or \n
* and NULL if it is not found within the limit
*/
char* extractline(char *s, int limit)
{
    // \r = CR (Carriage Return) // Used as a new line character in Unix
    // \n = LF (Line Feed) // Used as a new line character in Mac OS
    // \r\n = CR + LF // Used as a new line character in Windows
    int crlf; // position of next '\r' or '\n' or "\r\n"
    char *tok;
    // Clean up if no room left
    // If Windows
    tok = strtok (s, "\r\n"); // if it exists, will return pointer to the next string (null terminated) that exists
    if (tok)
    {
	crlf = strlen(tok); // get position of the newline character
	tok += crlf;
	return tok; // return pointer to the end of the string
    }
    // If UNIX
    tok = strtok (s, "\r");
    if (tok)
    {
	crlf = strlen(tok); // get position of the newline character
	tok += crlf;
	return tok; // return pointer to the end of the string
    }
    // If MAC OS
    tok = strtok (s, "\n");
    if (tok)
    {
	crlf = strlen(tok); // get position of the newline character
	tok += crlf;
	return tok; // return pointer to the end of the string
    }
    // if none found
    return NULL;
}

//============================================
// Server Functions (Wrapper)
//============================================
/**
* This function accepts a peer socket that is trying to connect
*/
int Accept(int fd, struct sockaddr *sa, socklen_t *salenptr)
{
    int  n;
    if ((n = accept(fd, sa, salenptr)) < 0)
    {
        perror("accept error");
        exit(1);
    }
    return(n);
}

//--------------------------------------------------------------------------------------

/**
* This function binds a host socket to the server
*/
void Bind(int fd, const struct sockaddr *sa, socklen_t salen)
{
    if (bind(fd, sa, salen) < 0)
    {
        perror("bind error");
        exit(1);
    }
}

//--------------------------------------------------------------------------------------

/**
* This function connects to a host socket
*/
int ConnectServ(int fd, const struct sockaddr *sa, socklen_t salen)
{
    int result;
    if ((result = connect(fd, sa, salen)) < 0)
    {
        perror("connect error");
        exit (1);
    }
    return(result);
}

//--------------------------------------------------------------------------------------

/**
* This function listens for peer sockets
*/
void Listen(int fd, int backlog)
{
    if (listen(fd, backlog) < 0)
    {
        perror("listen error");
        exit(1);
    }
}

//--------------------------------------------------------------------------------------

/**
* This function creates a socket
*/
int Socket(int family, int type, int protocol)
{
    int n;
    if ((n = socket(family, type, protocol)) < 0)
    {
        perror("socket error");
        exit(1);
    }
    return(n);
}

//--------------------------------------------------------------------------------------

/**
* This function closes a file descriptor
*/
void Close(int fd)
{
    if (close(fd) == -1)
    {
        perror("close error");
        exit(1);
    }
}
